/*
 * Copyright 2008-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.hasor.dataql.fx.js;

import net.hasor.core.AppContext;
import net.hasor.core.spi.SpiTrigger;
import net.hasor.dataql.FragmentProcess;
import net.hasor.dataql.Hints;
import net.hasor.dataql.fx.db.fxquery.DefaultFxQuery;
import net.hasor.dataql.fx.db.fxquery.FxQuery;
import net.hasor.dataway.DatawayService;
import net.hasor.utils.StringUtils;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 支持 js 的代码片段执行器。
 *
 * @author tdx.liuq
 * @version : 2020-09-26
 */
@Singleton
public class JsFragment implements FragmentProcess {
    @Inject
    protected AppContext appContext;
    @Inject
    protected SpiTrigger spiTrigger;

    // 静态化一个JS执行容器（单实例）
    private static final Context context = Context.newBuilder("js")
            .allowAllAccess(true).build();
    private static final ConcurrentHashMap<String, String> jsContMap = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        // init code here
        DatawayService dataway = appContext.getInstance(DatawayService.class);
        context.getBindings("js").putMember("QuickApi", dataway);
    }

    public List<Object> batchRunFragment(Hints hint, List<Map<String, Object>> params, String fragmentString) throws Throwable {
        if (params == null || params.size() == 0) {
            // 如果批量参数为空退：退化为 非批量
            return Collections.singletonList(this.runFragment(hint, Collections.emptyMap(), fragmentString));
        }
        if (params.size() == 1) {
            // 批量参数只有一组：退化为 非批量
            return Collections.singletonList(this.runFragment(hint, params.get(0), fragmentString));
        }

        List<Object> list = new ArrayList<>();
        for (Map<String, Object> paramMap : params)
            list.add(this.runFragment(hint, paramMap, fragmentString));

        return list;
    }

    /**
     * 分析 Js 代码
     */
    protected FxQuery analysisJs(Hints hint, String fragmentString) {
        return DefaultFxQuery.analysisSQL(fragmentString);
    }

    /**
     * 将 GraalVM 的 Value 对象转为 Java 的 Object 对象
     *
     * @param value Value对象
     * @return Java Object 对象
     */
    protected Object parseJavaObject(Value value) {
        if (value == null)
            return null;

        if (value.isBoolean())
            return value.asBoolean();
        else if (value.isString())
            return value.asString();
        else if (value.isNumber())
            if (value.toString().contains("."))
                return value.asDouble();
            else
                return value.asLong();
        else
            return value.toString();
    }

    @Override
    public Object runFragment(Hints hint, Map<String, Object> paramMap, String fragmentString) throws Throwable {
        // paramMap 消除 null
        if (paramMap == null)
            paramMap = new HashMap<>();

        // 处理参数
        FxQuery fxQuery = analysisJs(hint, fragmentString);
        String jsCode = fxQuery.buildQueryString(paramMap);

        // 执行 js 代码
        String keys = StringUtils.join(paramMap.keySet().toArray(), ",");
        Object[] values = paramMap.values().toArray();
        jsCode = "(function(" + keys + "){" + jsCode + "})";
        Value jsContext = context.eval("js", jsCode);
        Value result = jsContext.execute(values);
        return parseJavaObject(result);
    }
}